/*
 * Sync.c
 *      Author: DePhille
 */


#include "Sync.h"


/**
 * Synchronizes the FileSystem and the local Database for a given account.
 *
 * @param	account	The account to synchronize.
 */
static void	PC_Sync_FS(const PurpleAccount* account) {
	gchar*		tableName;	// Clean-up: Yes
	GList*		fsLogs;		// Clean-up: No
	GList*		dbLogs;		// Clean-up: No
	GList*		commonLogs;	// Clean-up: No
	gint64		startStamp;	// Clean-up: No

	// Start profile timer:
	startStamp = PC_Util_GetMiliSeconds();

	// Check input:
	if (!account) {
		purple_debug_warning("PidginCote", "PC_Sync_FS: Missing 'account' parameter.\r\n");
		return;
	}

	// Assign default values:
	fsLogs = NULL;
	dbLogs = NULL;
	commonLogs = NULL;

	// Create Log Table if it does not exist yet:
	tableName = PC_LogDB_GetTableName(account->protocol_id, PC_Util_ParseUsername(account->username));
	if (!tableName) {
		purple_debug_error("PidginCote", "PC_Sync_FS: Unable to get tableName.\r\n");
		return;
	} else if (!PC_LogDB_TableExists(tableName)) {

		// Table does not exist, create it:
		if (!PC_DBTable_Logs_MakeTable(tableName)) {
			purple_debug_error("PidginCote", "PC_Sync_FS: Unable to make Logs table for the given account.\r\n");
			return;
		}
	}

	// Clean-up:
	g_free(tableName);

	// Fetch DB and FS loglist:
	purple_debug_info("PidginCote", "PC_Sync_FS: Synchronizing FS and DB for account %s/%s...\r\n", account->protocol_id, PC_Util_ParseUsername(account->username));
	PC_MiniLog_ScanDir(purple_log_get_log_dir(PURPLE_LOG_IM, NULL, account), &fsLogs);
	dbLogs = PC_DBTable_Logs_FetchMiniLogs(account);

	// Filter out common entries:
	commonLogs = PC_MiniLog_Filter(&dbLogs, &fsLogs);

	// Perform changes:
	purple_debug_info("PidginCote", "PC_Sync_FS: Found %i deleted logs in the database.\r\n", g_list_length(dbLogs));
	dbLogs = PC_Sync_FS_Delete(account, dbLogs);
	purple_debug_info("PidginCote", "PC_Sync_FS: Found %i new logs on the FileSystem.\r\n", g_list_length(fsLogs));
	fsLogs = PC_Sync_FS_Insert(account, fsLogs);
	purple_debug_info("PidginCote", "PC_Sync_FS: Found %i common logs.\r\n", g_list_length(commonLogs));
	commonLogs = PC_Sync_FS_Update(account, commonLogs);

	// Check if all lists are empty:
	if (fsLogs) {
		purple_debug_warning("PidginCote", "PC_Sync_FS: %i FileSystem-only logs were not scanned.\r\n", g_list_length(fsLogs));
	}
	if (dbLogs) {
		purple_debug_warning("PidginCote", "PC_Sync_FS: %i Database-only logs were not scanned.\r\n", g_list_length(dbLogs));
	}
	if (commonLogs) {
		purple_debug_warning("PidginCote", "PC_Sync_FS: %i Common logs were not scanned.\r\n", g_list_length(commonLogs));
	}

	// Debug output:
	purple_debug_info("PidginCote", "PC_Sync_FS: Synchronized account with FS in %li miliseconds.\r\n", PC_Util_GetMiliSeconds() - startStamp);
}


/**
 * Pops MiniLogs from the given list and inserts them in the database.
 *
 * @param	miniLogs	The list of minilogs to insert.
 * @returns	The new start of the list.
 */
static GList* PC_Sync_FS_Insert(const PurpleAccount* account, GList* miniLogs)  {
	PC_MiniLog*	miniLog;	// Clean-up: Yes
	PC_Log*		log;		// Clean-up: Yes

	// Check input:
	if(!account) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Insert: Missing 'account' parameter.\r\n");
		return miniLogs;
	} else if (!account->protocol_id) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Insert: Missing 'account->protocol_id' parameter.\r\n");
		return miniLogs;
	} else if (!account->username) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Insert: Missing 'account->username' parameter.\r\n");
		return miniLogs;
	}

	// Loop trough list:
	while (miniLogs) {
		// Cast:
		miniLog = (PC_MiniLog*) miniLogs->data;
		if (!miniLog) {
			purple_debug_warning("PidginCote", "PC_Sync_FS_Insert: Invalid 'miniLog' found. Memory leak?\r\n");
		} else {

			// Make PC_Log:
			log = PC_Log_FromMiniLog(miniLog);
			if (!log) {
				purple_debug_warning("PidginCote", "PC_Sync_FS_Insert: Unable to convert miniLog to Log.\r\n");
			} else {
				// Fill attributes:
				log->protocol = g_strdup(account->protocol_id);
				log->account = PC_Util_ParseUsername(account->username);
				PC_Log_LoadRecipient(log);
				PC_Log_LoadStartTime(log);
				PC_Log_LoadLastChange(log);
				log->revision = 0;

				// Insert:
				if (!PC_DBTable_Logs_Insert(log)) {
					purple_debug_warning("PidginCote", "PC_Sync_FS_Insert: Unable to insert log into Database.\r\n");
				}

				// Clean-up:
				PC_Log_Free(log);
				log = NULL;
			}

			// Clean-up:
			PC_MiniLog_Delete(miniLog);
			miniLog = NULL;
			miniLogs->data = NULL;
		}

		// Next item:
		miniLogs = g_list_delete_link(miniLogs, miniLogs);
	}

	// Return list:
	return miniLogs;
}



/**
 * Pops MiniLogs from the given list and deletes them from the database.
 *
 * @param	miniLogs	The list of minilogs to delete.
 * @returns	The new start of the list.
 */
static GList* PC_Sync_FS_Delete(const PurpleAccount* account, GList* miniLogs)  {
	PC_MiniLog*	miniLog;	// Clean-up: Yes
	PC_Log*		log;		// Clean-up: Yes

	// Check input:
	if(!account) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Delete: Missing 'account' parameter.\r\n");
		return miniLogs;
	} else if (!account->protocol_id) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Delete: Missing 'account->protocol_id' parameter.\r\n");
		return miniLogs;
	} else if (!account->username) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Delete: Missing 'account->username' parameter.\r\n");
		return miniLogs;
	}

	// Loop trough list:
	while (miniLogs) {
		// Cast:
		miniLog = (PC_MiniLog*) miniLogs->data;
		if (!miniLog) {
			purple_debug_warning("PidginCote", "PC_Sync_FS_Delete: Invalid 'miniLog' found. Memory leak?\r\n");
		} else {

			// Make PC_Log:
			log = PC_Log_FromMiniLog(miniLog);
			if (!log) {
				purple_debug_warning("PidginCote", "PC_Sync_FS_Delete: Unable to convert miniLog to Log.\r\n");
			} else {
				// Fill attributes:
				log->protocol = g_strdup(account->protocol_id);
				log->account = PC_Util_ParseUsername(account->username);

				// Delete:
				if (!PC_DBTable_Logs_Delete(log)) {
					purple_debug_warning("PidginCote", "PC_Sync_FS_Delete: Unable to delete log from Database.\r\n");
				}

				// Clean-up:
				PC_Log_Free(log);
				log = NULL;
			}

			// Clean-up:
			PC_MiniLog_Delete(miniLog);
			miniLog = NULL;
			miniLogs->data = NULL;
		}

		// Next item:
		miniLogs = g_list_delete_link(miniLogs, miniLogs);
	}

	// Return list:
	return miniLogs;
}



/**
 * Pops MiniLogs from the given list and sets their revision to 0 (= mark as 'update needed') in the database.
 *
 * @param	miniLogs	The list of minilogs to update.
 * @returns	The new start of the list.
 */
static GList*	PC_Sync_FS_Update(const PurpleAccount* account, GList* miniLogs)  {
	PC_MiniLog*	miniLog;	// Clean-up: Yes
	PC_Log*		log;		// Clean-up: Yes

	// Check input:
	if(!account) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Update: Missing 'account' parameter.\r\n");
		return miniLogs;
	} else if (!account->protocol_id) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Update: Missing 'account->protocol_id' parameter.\r\n");
		return miniLogs;
	} else if (!account->username) {
		purple_debug_warning("PidginCote", "PC_Sync_FS_Update: Missing 'account->username' parameter.\r\n");
		return miniLogs;
	}

	// Loop trough list:
	while (miniLogs) {
		// Cast:
		miniLog = (PC_MiniLog*) miniLogs->data;
		if (!miniLog) {
			purple_debug_warning("PidginCote", "PC_Sync_FS_Update: Invalid 'miniLog' found. Memory leak?\r\n");
		} else {

			// Check if the FS log was changed:
			if (PC_MiniLog_HasChanged(miniLog)) {

				// Make PC_Log:
				log = PC_Log_FromMiniLog(miniLog);
				if (!log) {
					purple_debug_warning("PidginCote", "PC_Sync_FS_Update: Unable to convert miniLog to Log.\r\n");
				} else {
					// Fill attributes:
					log->protocol = g_strdup(account->protocol_id);
					log->account = PC_Util_ParseUsername(account->username);
					PC_Log_LoadRecipient(log);
					PC_Log_LoadStartTime(log);
					PC_Log_LoadLastChange(log);
					log->revision = 0;

					// Update:
					if (!PC_DBTable_Logs_Update(log, log)) {
						purple_debug_warning("PidginCote", "PC_Sync_FS_Update: Unable to update log.\r\n");
					}

					// Clean-up:
					PC_Log_Free(log);
					log = NULL;
				}
			}

			// Clean-up:
			PC_MiniLog_Delete(miniLog);
			miniLog = NULL;
			miniLogs->data = NULL;
		}

		// Next item:
		miniLogs = g_list_delete_link(miniLogs, miniLogs);
	}

	// Return list:
	return miniLogs;
}


/**
 * Does a Full synchronization cycle between the local and external database. (i.e. Compare all logs).
 *
 * @param	account	The PurpleAccount for which to synchronize.
 */
static void	PC_Sync_FullDB(const PurpleAccount* account) {
	// Check input:
	if (!account) {
		purple_debug_warning("PidginCote", "PC_Sync_FullDB: Missing 'account' parameter.\r\n");
		return;
	} else if (!account->protocol_id) {
		purple_debug_warning("PidginCote", "PC_Sync_FullDB: Missing 'account->protocol_id' parameter.\r\n");
		return;
	} else if (!account->username) {
		purple_debug_warning("PidginCote", "PC_Sync_FullDB: Missing 'account->username' parameter.\r\n");
		return;
	}

	// Firstly, upload all logs with revision 0:
	PC_Sync_UploadAll(account);

	// Send LogList request with timestamp 0:
	// TODO: Enable timestamp 0 in loglist request.
	PC_Net_LogList(account->protocol_id, PC_Util_ParseUsername(account->username));
}



/**
 * Synchronizes the local DB with a LogList received from the server.
 * 	Pops all PC_Logs from the given logList.
 *
 * @param logList	The logList to synchronize
 * @returns The remaining list.
 */
static GList* PC_Sync_LogList(GList* logList) {
	GList*			curItem;	// Clean-up: Yes
	PC_Log*			curLog;		// Clean-up: Yes
	int				revision;	// Clean-up: No

	if (!logList) {
		return logList;
	}

	// Cycle trough list:
	curItem = logList;
	while (curItem) {
		// Cast:
		curLog = (PC_Log*) curItem->data;
		if (!curLog) {
			purple_debug_warning("PidginCote", "PC_Sync_LogList: Invalid 'PC_Log' detected in logList. Memory leak?\r\n");
		} else {

			// Try to fetch the log from the DB and compare it:
			revision = curLog->revision;
			if (PC_DBTable_Logs_FillIn(curLog)) {
				// Check revision:
				if (revision > curLog->revision) {
					// New log, download:
					PC_Net_Download(curLog);
				} else if (revision == curLog->revision) {
					// Log is up-to-date:
					PC_Log_Free(curLog);
				} else {
					purple_debug_warning("PidginCote", "PC_Sync_LogList: Log in DB is newer than the one on the server?\r\n");
					PC_Log_Free(curLog);
				}
			} else {
				// New log, fill in a fileName and download:
				PC_Log_LoadFileName(curLog);
				PC_Net_Download(curLog);
			}

			// Clean-up:
			curLog = NULL;
		}

		// Next item:
		curItem->data = NULL;
		curItem = g_list_delete_link(curItem, curItem);
	}

	// Return remaining list:
	return curItem;
}


/**
 * Uploads all logs with revision 0 to the server.
 *
 * @param account The account to synchronize.
 */
static void	PC_Sync_UploadAll(const PurpleAccount* account) {
	GList*	logList;	// Clean-up: Yes
	PC_Log*	curLog;		// Clean-up: Yes

	// Check input:
	if (!account) {
		purple_debug_warning("PidginCote", "PC_Sync_UploadAll: Missing 'account' parameter.\r\n");
		return;
	}

	// Get list of logs with revision 0:
	logList = PC_DBTable_Logs_GetRev0(account);

	// Cycle trough all logs:
	while (logList) {
		// Cast:
		curLog = (PC_Log*) logList->data;
		if (!curLog) {
			purple_debug_warning("PidginCote", "PC_Sync_UploadAll: Invalid 'PC_Log' detected in logList. Memory leak?\r\n");
		} else {

			// Upload:
			PC_Net_Upload(curLog);

			// Clean-up:
			curLog = NULL;
		}

		// Next item:
		logList->data = NULL;
		logList = g_list_delete_link(logList, logList);
	}
}
